#ifndef _CJOBDISPATCHER_H_
#define _CJOBDISPATCHER_H_

#include <tbb/flow_graph.h>
#include <tbb/reader_writer_lock.h>
#include <tbb/mutex.h>

#include <windows.h>
#include <vector>
#include <cassert>

#include "CCGen.h"
#include "Math.h"

enum ECubeFaceIndexes
{
	CP_FACE_X_POS = 0,
	CP_FACE_X_NEG = 1,
	CP_FACE_Y_POS = 2,
	CP_FACE_Y_NEG = 3,
	CP_FACE_Z_POS = 4,
	CP_FACE_Z_NEG = 5
};

enum ECubeEdge
{
	CP_EDGE_LEFT    = 0,
	CP_EDGE_RIGHT   = 1,
	CP_EDGE_TOP     = 2,
	CP_EDGE_BOTTOM  = 3,
};

template <typename T>
struct TLUTData
{
	T *data;

	unsigned int width;
	unsigned int height;

	TLUTData()
		:width(0),
		height(0),
		data(nullptr)
	{}

	~TLUTData()
	{
		if (data)
		{
			_aligned_free(data);
		}
	}

	T* GetPtr(unsigned int x, unsigned int y)
	{
		return &data[y * width + x];
	}

	T* Allocate(unsigned int _width, unsigned int _height)
	{
		width = _width;
		height = _height;

		if (data)
		{
			data = (T*)_aligned_realloc(data, sizeof(T)* width * height, 16);
		}
		else
		{
			data = (T*)_aligned_malloc(sizeof(T)* width * height, 16);
		}


		return data;
	}
};

struct STexture;

struct STapdirLUT
{
	tbb::atomic<ELUTStatus> status;
	TLUTData<Vec4> *dataPtrs;
	unsigned int mipCount;

#ifdef USE_GPU
	STexture *gpuDataPtrs;
#endif

	STapdirLUT()
	{
#ifdef USE_GPU
		gpuDataPtrs = nullptr;
#endif
		status = eLUTS_invalid;
		dataPtrs = nullptr;
		mipCount = 0;
	}

	~STapdirLUT();

	void Init(unsigned int width, unsigned int height)
	{
		SAFE_DELETE_ARRAY(dataPtrs);

		mipCount = (unsigned int)CGen::log2((float)CGen::max(width, height)) + 1;

		dataPtrs = new TLUTData<Vec4>[mipCount * 6];

		for (unsigned int i_face = 0, lut = 0; i_face < 6; i_face++)
		{
			unsigned int mipWidth = width;
			unsigned int mipHeight = height;

			for (unsigned int i_mip = 0; i_mip < mipCount; i_mip++)
			{
				dataPtrs[lut++].Allocate(mipWidth, mipHeight);

				mipWidth = mipWidth >> 1;
				mipHeight = mipHeight >> 1;
			}
		}
	}
};


struct SFilterExtentsLUT
{
	struct FilterExtent
	{
		__int16 minTexelX[6], maxTexelX[6];
		__int16 minTexelY[6], maxTexelY[6];

		void Expand(int faceId, __int16 x, __int16 y);

		void ClampMin(int faceId, __int16 x, __int16 y);

		void ClampMax(int faceId, __int16 x, __int16 y);

		void Clear()
		{
			minTexelX[0] = minTexelX[1] = minTexelX[2] = minTexelX[3] = minTexelX[4] = minTexelX[5] = 32767;
			minTexelY[0] = minTexelY[1] = minTexelY[2] = minTexelY[3] = minTexelY[4] = minTexelY[5] = 32767;
			maxTexelX[0] = maxTexelX[1] = maxTexelX[2] = maxTexelX[3] = maxTexelX[4] = maxTexelX[5] = -32768;
			maxTexelY[0] = maxTexelY[1] = maxTexelY[2] = maxTexelY[3] = maxTexelY[4] = maxTexelY[5] = -32768;
		}

		bool IsEmpty(int face)
		{
			if (minTexelX[face] >= maxTexelX[face] || minTexelY[face] >= maxTexelY[face])
			{
				return true;
			}
			else
			{
				return false;
			}
		}
	};
	tbb::atomic<ELUTStatus> status;
	TLUTData< FilterExtent > *dataPtrs;
	unsigned int mipCount;

#ifdef USE_GPU
	STexture *gpuDataPtrs;
#endif

	SFilterExtentsLUT()
	{
#ifdef USE_GPU
		gpuDataPtrs = nullptr;
#endif
		status = eLUTS_invalid;
		dataPtrs = nullptr;
		mipCount = 0;
	}

	~SFilterExtentsLUT();

	void Init(unsigned int width, unsigned int height);
};

struct SFilterJobInternal
{
	SFilterJob job;
	SCubeMap *vectorCubemap; //To convert all pixel formats to rgba floats(might be __m128)
	volatile unsigned int internalCounter;

	SFilterJobInternal()
	{
		vectorCubemap = nullptr;
		internalCounter = 0;
	}

	~SFilterJobInternal()
	{
		SAFE_DELETE(vectorCubemap);
	}
};

struct SFilterStateData
{
	SFilterStateDesc desc;
	unsigned int filterSizes[MAX_CUBEMAP_MIPLEVEL];
	float dotpThreshold[MAX_CUBEMAP_MIPLEVEL];
	float specularPowers[MAX_CUBEMAP_MIPLEVEL];
};

struct SFilterSurfaceJob
{
	SCubeFace inputCubeFaces[6];
	SCubeFace outputCubeFace;
	int outputFaceIndex;
	SFilterState *filterState;
	const SFilterStateData *filterStateData;
	//volatile unsigned int *jobCounter;
	SFilterJobInternal *parentJob;
};

struct SComputeTapdirLUTDesc
{
	int stateIndex;
};

extern Vec4 gFace2DMapping[6][3];

class CCgenUtil
{

public:

	static void TexelCoordToVect(unsigned int face, float a_U, float a_V, int a_Size, Vec4 &outVec, EFixupType fixupType);
	static void DetermineFilterExtents(Vec4 *centerDir, int a_SrcSize, int a_BBoxSize,	SFilterExtentsLUT::FilterExtent *outFilterExtents);
	static void VectToTexelCoord(Vec4 *vector, int a_Size, int *a_FaceIdx, int *a_U, int *a_V);
	static float GetBaseFilterAngle(float cosinePower);
	static float TexelCoordSolidAngle(float a_U, float a_V, int a_Size);
	static float AreaElement(float x, float y)
	{
		return atan2(x * y, sqrt(x * x + y * y + 1));
	}
};

class CJobDispatcher
{

private:

	tbb::flow::graph *filterGraph;

	tbb::flow::buffer_node<SFilterJobInternal*> *filterJobBufferNode;

	tbb::flow::function_node<SFilterJobInternal*> *matchWithLUTNode;

	tbb::flow::buffer_node<SFilterState> *computeTapDirLUTBufferNode;

	tbb::flow::buffer_node<SFilterState> *computeFilterExtentsLUTBufferNode;

	tbb::flow::buffer_node<SFilterJobInternal*> *vectorizeCubemapBufferNode;

	tbb::flow::function_node<SFilterJobInternal*> *vectorizeCubemapNode;

	tbb::flow::function_node<SFilterState> *computeTapDirLUTNode;

	tbb::flow::function_node<SFilterState> *computeFilterExtentsLUTNode;

	tbb::flow::buffer_node<SFilterSurfaceJob> *filterLevelJobBufferNode;

	tbb::flow::function_node<SFilterSurfaceJob> *processFilterJobNode;

	std::vector<SFilterExtentsLUT*> filterExtentsLuts;
	std::vector<STapdirLUT*> tapdirLuts;
	std::vector<float**> powTables;
	std::vector<SFilterStateData*> filterStates;

	tbb::reader_writer_lock stateMutex;
	tbb::mutex activeJobMutex;

	volatile unsigned int totalJobCount;
	volatile unsigned int processedJobCount;

	std::vector<SFilterJobInternal*> activeJobList;

	void AddActiveJob(SFilterJobInternal* newJob)
	{
		activeJobMutex.lock();
		activeJobList.push_back(newJob);
		activeJobMutex.unlock();
	}

#ifdef USE_GPU
	CCGenGpu *gpuHandler;
#endif

public:

	CJobDispatcher();
	~CJobDispatcher();

	void Init();
	void BuildGraph();
	STapdirLUT *GetTapDirLUT(const SFilterState* filterState);
	void RemoveActiveJobAndNotify(SFilterJobInternal *internalJob);
	void ComputeTapDirLUT(const SFilterState* filterState);
	void ComputeFilterExtentsLUT(const SFilterState* filterState);
	void VectorizeCubemap(SFilterJobInternal *job);
	bool CheckFilterJob(const SFilterJob *job);
	SFilterExtentsLUT *GetFilterExtentsLUT(const SFilterState* filterState);

	bool CreateFilterState(const SFilterStateDesc* stateDesc, SFilterState** outState);

	void DispatchNewJob(const SFilterJob* new_job);

	void DispatchFilterSurfaceJob(const SFilterSurfaceJob *new_job);
	bool IsGpuEnabled();
	void WaitForAll();
	const SFilterStateData* GetFilterStateDesc(const SFilterState *state)
	{
		tbb::reader_writer_lock::scoped_lock_read lockState(stateMutex);
		return filterStates[state->GetStateID()];
	}
	bool EnableGpu();

	class CMatchLUTFunctor
	{
		CJobDispatcher *dispatcher;

	public:
		CMatchLUTFunctor(CJobDispatcher *disp)
			:dispatcher(disp)
		{}

		void operator ()(SFilterJobInternal *job);
	};

	class CComputeTapDirLUTFunctor
	{
		CJobDispatcher *dispatcher;

	public:
		CComputeTapDirLUTFunctor(CJobDispatcher *disp)
			:dispatcher(disp)
		{}

		void operator ()(SFilterState filterState);
	};

	class CComputeFilterExtentsLUTFunctor
	{
		CJobDispatcher *dispatcher;

	public:
		CComputeFilterExtentsLUTFunctor(CJobDispatcher *disp)
			:dispatcher(disp)
		{}

		void operator ()(SFilterState filterState);
	};	

	class CProcessFilterJobFunctor
	{
		CJobDispatcher *dispatcher;

	public:
		CProcessFilterJobFunctor(CJobDispatcher *disp)
			:dispatcher(disp)
		{}

		void operator ()(SFilterSurfaceJob filterLevelJob);
	};

	class CVectorizeCubemapFunctor
	{
		CJobDispatcher *dispatcher;

	public:
		CVectorizeCubemapFunctor(CJobDispatcher *disp)
			:dispatcher(disp)
		{}

		void operator ()(SFilterJobInternal *filterJob);
	};

	friend class CCGenGpu;
};

#endif